/*  ----------------------------------------------------------------------------
 *  Arello Mobile
 *  Fast 3d Engine
 *  ----------------------------------------------------------------------------
 * Except where otherwise noted, this work is licensed under
 * http://creativecommons.org/licenses/by/3.0
 */

#include "AbstractSequence.h"

#include <math/utl.h>
#include <xml/Stream.h>
#include <lib3d/hard/Hardware.h>
#include <system/StringFunctions.h>

#include <boost/bind.hpp>
#include <algorithm>
#include <system/assert.h>
#include <xml/Saver.h>

namespace liba {
namespace lib3d {
namespace anim2d {

AbstractSequence::AbstractSequence()
	: frame_start(0)
	, frame_end(0)
	, frame_time(0.3)
	, width(0)
	, height(0)
	, loop_type(NONE)
	, loopDelay(0.0)
{ }

AbstractSequence::AbstractSequence(const AbstractSequence& rhs)
	: name(rhs.name)
	, frame_start(rhs.frame_start)
	, frame_end(rhs.frame_end)
	, image_name(rhs.image_name)
	, frame_time(rhs.frame_time)
	, height(rhs.height)
	, width(rhs.width)
	, loopDelay(rhs.loopDelay)
	, frameTimes(rhs.frameTimes)
	, irregularDurations(rhs.irregularDurations)
	, loop_type(rhs.loop_type)
{ }

AbstractSequence::~AbstractSequence()
{ }

void AbstractSequence::on_save_attributes(xml::SaverBase * saver) const
{
	saver->save_attribute("Name", name);
	saver->save_attribute("Image", image_name);
	saver->save_attribute("FrameStart", frame_start);
	saver->save_attribute("FrameEnd", frame_end);
	saver->save_attribute("Width", width);
	saver->save_attribute("Height", height);
	saver->save_attribute("FrameTime", frame_time);

	if(loop_type != NONE)
		saver->save_attribute("LoopType", loop_type);

	if(loopDelay != 0.0)
		saver->save_attribute("LoopDelay", loopDelay);
}

bool AbstractSequence::on_attribute(xml::Provider * prov, const std::string & name, const std::string & value)
{
	if( name == "Name" )
	{
		this->name = Atom(value);
		return true;
	}
	if( name == "Image" )
	{
		image_name = value;
		return true;
	}
	if( name == "FrameStart" )
		return converter::convert( value, &frame_start );
	if( name == "FrameEnd" )
		return converter::convert( value, &frame_end );
	if( name == "FramesInFile" )
		return true; // backwards compatibility
	if( name == "AllInOne" )
	{
		bool val;
		converter::convert(value, &val);
		LIBA_ASSERT(!val);
		return true; // backwards compatibility
	}
	if( name == "Width" )
		return converter::convert( value, &width );
	if( name == "Height" )
		return converter::convert( value, &height );
	if(name == "FrameTime")
		return converter::convert(value, &frame_time);
	if (name == "ImageIndexLength")
		return true; // backwards compatibility
	if (name == "LoopType")
		return converter::convert(value, &loop_type);
	if (name == "LoopDelay")
		return converter::convert(value, &loopDelay);
	return false;
}

class FrameLoader
	: public xml::ObjectANSI
{
public:
	FrameLoader()
		: object(0)
	{ }

	void SetObject(std::pair<size_t, double>* value) { this->object = value; }

	bool on_attribute(xml::Provider * prov, const std::string & name, const std::string & value)
	{
		if (name == "Index")
			return converter::convert(value, &object->first);
		if (name == "Time")
			return converter::convert(value, &object->second);

		return false;
	}
private:
	std::pair<size_t, double>* object;
};

bool AbstractSequence::on_node(xml::Provider * prov, const std::string & name)
{
	if (name == "Frame")
	{
		std::pair<size_t, double> newFrame;
		FrameLoader ldr;
		ldr.SetObject(&newFrame);
		if (!prov->parse_object(&ldr))
			return false;

		irregularDurations.push_back(newFrame);
		return true;
	}

	return false;
}

namespace {
bool firstIs(size_t constValue, const std::pair<size_t, double>& pair)
{
	return pair.first == constValue;
}
}

void AbstractSequence::ComputeFrameTimes()
{
	this->frameTimes.Reset();

	for (unsigned i = frame_start; i <= frame_end; ++i)
	{
		std::vector<std::pair<size_t, double> >::const_iterator irrDur =
			std::find_if(
			irregularDurations.begin(), 
			irregularDurations.end(),
			boost::bind(&firstIs, i, _1));

		if (irrDur == irregularDurations.end())
			this->frameTimes.AddFrame(frame_time);
		else
			this->frameTimes.AddFrame(irrDur->second);
	}
}

bool AbstractSequence::on_end_node(xml::Provider * prov)
{
	ComputeFrameTimes();
	load_image( image_name );
	return true;
}

converter::EnumPair<char, AbstractSequence::LoopType> AbstractSequence::LoopTypeNames[] = 
{
	converter::MakeEnumPair( "NONE", AbstractSequence::NONE ),
	converter::MakeEnumPair( "FORWARD", AbstractSequence::FORWARD ),
	converter::MakeEnumPair( "BACKWARD", AbstractSequence::BACKWARD ),
	converter::MakeEnumPair( "PINGPONG", AbstractSequence::PINGPONG ),
	0
};

} // namespace anim2d
} // namespace lib3d
} // namespace liba

